package dk.au.cs.peterbn.processing.compilationJobs;

import dk.au.cs.peterbn.annotations.Pattern;
import dk.au.cs.peterbn.patterns.TypeRole;
import dk.au.cs.peterbn.processing.PatternInstance;
import dk.au.cs.peterbn.processing.ProcessingJob;
import dk.au.cs.peterbn.processing.ProcessingUtil;

import javax.lang.model.element.*;
import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic;
import java.util.*;

/**
 * This job asserts that all role annotations target the correct type.
 *
 * This only generates a warning, as it is common to mix the targets,
 * and it can be completely disabled for a particular annotations with the
 * overrideTarget property.
 */
public class CheckAllRolesCorrectTarget implements ProcessingJob {

    private final Map<String, PatternInstance> instanceMap;
    private final ProcessingUtil util;

    public CheckAllRolesCorrectTarget(Map<String,PatternInstance> instanceMap, ProcessingUtil util) {
        this.instanceMap = instanceMap;
        this.util = util;
    }


    @Override
    public void process() {
        Set<Element> elementsAnnotatedWith = new HashSet<Element>();
        Collection<PatternInstance> values = instanceMap.values();
        for (PatternInstance value : values) {
            elementsAnnotatedWith.addAll(value.getElements());
        }
        Set<TypeElement> types = ElementFilter.typesIn(elementsAnnotatedWith);
        for (TypeElement type : types) {
            checkType(type);
        }

    }

    private void checkType(TypeElement type) {
        List<AnnotationMirror> patternAnnotations = util.getPatternAnnotationMirrors(type);
        for (AnnotationMirror patternAnnotation : patternAnnotations) {
            String overrideTarget = util.getValue(patternAnnotation, Pattern.Fields.OVERRIDE_TARGET);
            if (overrideTarget.length() > 0) {
                TypeRole typeRole = util.getTypeRole(type, patternAnnotation);
                boolean correctTarget = isCorrectTarget(type, typeRole);
                if (!correctTarget) {
                    //Really just a warning.
                    util.printMessage(Diagnostic.Kind.WARNING, "Role " + typeRole.getName() + " applied to wrong target type",type,patternAnnotation);
                }
            }

        }
    }

    private boolean isCorrectTarget(TypeElement type, TypeRole typeRole) {
        TypeRole.Target target = typeRole.getTarget();
        ElementKind kind = type.getKind();
        boolean isAbstract = type.getModifiers().contains(Modifier.ABSTRACT);
        switch (target) {

            case ABSTRACT:
                return kind == ElementKind.INTERFACE ||
                    (kind == ElementKind.CLASS && isAbstract);
            case CONCRETE:
                return kind == ElementKind.CLASS && !isAbstract;
            case ANY:
                return true;
            default:
                assert false : "Missing case";
                return false;
        }
    }

}
